<template>
	<view class="chat-container" :style="pageHeight">
		<NavBar :isRight="true" :isLeft="true" :ricon="'more-filled'" class="nav-bar">
			<template #lf>
				<navigator class="nav-bar-lf" open-type="navigateBack" hover-class="none">
					<uni-icons type="left" size="25"></uni-icons>
					<image class="nav-bar-lf-avatar" :src="targetUserAvatar" mode="aspectFill"></image>
					<view class="nav-bar-lf-name">
						<view>{{targetUserName}}</view>
						<view class="sub">
							<view class="status"></view>在线中
						</view>
					</view>
				</navigator>
			</template>
		</NavBar>
		<u-loading-page bgColor="#F7F7F7" :loading="loading" fontSize="14" loading-mode="semicircle"></u-loading-page>
		<!-- @refresherrefresh="handleRefresherRefresh" -->
		<!-- @scrolltoupper="onScrollToUpper"  -->
		<scroll-view class="chat-scroll-view" scroll-y refresher-background="transparent" @scroll="handleScroll"
			:scroll-with-animation="isScrollViewAnimated" :refresher-triggered="scrollView.refresherTriggered"
			:scroll-into-view="scrollView.intoView">
			<view class="talk-list">
				<view v-for="(item,index) in talkList" :key="item.timestamp+'-'+index" :id="'msg-'+item.id"
					class="message-group">
					<view class="message-time" v-if="shouldShowTimestamp(item, index)">
						{{item.time}}
					</view>

					<view v-if="item.contentType === 'system_message_custom_timestamp'" class="system-message-wrapper">
						<view class="message-time">{{ item.customTimestamp }}</view>
						<view class="system-message-content">{{ item.content }}</view>
					</view>

					<view v-else-if="item.contentType === 'system'" class="system-message">
						{{ item.content }}
					</view>

					<view v-else class="message-item" :class="item.type === 1 ? 'sent' : 'received'">
						<image @click="onOther(item.userId)"
							:src="item.type === 1 ? (item.avatar || defaultMyAvatar) : (item.avatar || defaultOtherAvatar)"
							mode="aspectFill" class="avatar"></image>
						<!-- 视频消息渲染 -->
						<template v-if="item.contentType === 'video'">
							<view class="video-wrapper">
								<!-- 使用 DomVideoPlayer 渲染视频 -->
								<DomVideoPlayer :src="item.content"
									:poster="item.thumb || '/static/chat/video_poster_default.png'"
									:style="{width: '300rpx', height: '400rpx'}" :controls="true" :autoplay="false"
									:muted="true" :show-center-play-btn="true" />
								<view v-if="item.status === 'uploading' || item.status === 'sending'"
									class="video-loading-overlay">
									<u-loading-icon mode="circle" size="30"></u-loading-icon>
								</view>
								<view v-if="item.status === 'failed'" class="video-failed-overlay">
									<u-icon name="error-circle" color="#fa3534" size="30"></u-icon>
								</view>
							</view>
						</template>
						<!-- 图片消息渲染 -->
						<template v-else-if="item.contentType === 'img'">
							<view class="image-wrapper">
								<image :src="item.content" mode="widthFix" class="message-image"
									@click="previewImage(item.content)"></image>
								<view v-if="item.status === 'uploading' || item.status === 'sending'"
									class="image-loading-overlay">
									<u-loading-icon mode="circle" size="24"></u-loading-icon>
								</view>
								<view v-if="item.status === 'failed'" class="image-failed-overlay">
									<u-icon name="error-circle" color="#fa3534" size="24"></u-icon>
								</view>
							</view>
							<!-- <image :src="item.content" mode="widthFix" class="message-image" @click="previewImage(item.content)"></image> -->
						</template>
						<view class="message-bubble-wrapper" v-else>
							<view class="message-bubble">

								<!-- 语音消息渲染 -->
								<template v-if="item.contentType === 'audio'">
									<!-- 给 voice-content 也绑定 is-playing 类，方便整体控制 -->
									<view class="voice-content" @click="playVoiceMessage(item)"
										:class="{'is-playing': playingMessageId === item.id}">
										<!-- 播放/暂停按钮 -->
										<view class="play-pause-btn"></view>

										<!-- 动态竖线 -->
										<view class="sound-bars">
											<span></span>
											<span></span>
											<span></span>
											<span></span>
											<span></span>
										</view>

										<!-- 时长 -->
										<text class="voice-duration">{{ item.duration }}''</text>
									</view>
								</template>
								<template v-else>
									<u-tooltip :text="item.content" :buttons="['删除','引用']"
										:color="item.type === 1 ? '#000000' : '#000000'"
										:bgColor="item.type === 1 ? '#B0E3FF' : '#FFFFFF'" @click="onTip($event,item)"
										overlayClassName="custom-tooltip-overlay"></u-tooltip>
								</template>
							</view>
							<view class="content-quote" v-if="item.quoteInfo && item.quoteInfo.name">
								{{item.quoteInfo.name}}：{{item.quoteInfo.content}}
							</view>
						</view>
					</view>
				</view>
			</view>
		</scroll-view>

		<view class="bottom-bar-menu" v-if="this.showList">
			<view class="bottom-bar-menu-left" @click="toggleBottomMenu">
				<view class="bottom-bar-menu-left-top">
					<u-icon :name="isBottomMenuExpanded ? 'arrow-left' : 'arrow-right'" color="rgba(0, 0, 0, 1)"
						size="20">
					</u-icon>
				</view>
				<view class="bottom-bar-menu-left-bottom">
					{{ isBottomMenuExpanded ? '收起' : '展开' }}
				</view>
			</view>

			<!-- 添加一个容器来包装菜单项 -->
			<view class="menu-items-container" :style="{ display: 'flex', flexGrow: 1, overflow: 'hidden' }">
				<view class="menu-item" @click="handleToCreate(menu.title)" v-for="(menu,index) in bottomMenu"
					:key="index" :class="{
		        'menu-item-hidden': !isBottomMenuExpanded || !isBottomMenuInitialized
		      }" :style="{
		        transitionDelay: (index * 50) + 'ms',
		        marginLeft: index === 0 ? '20rpx' : '20rpx' // 给每个菜单项添加左边距
		      }">
					<view class="menu-icon">
						<image :src="menu.icon" mode=""></image>
					</view>
					<view class="menu-title">
						{{ menu.title }}
					</view>
				</view>
			</view>
		</view>


		<view class="bottom-bar">
			<view class="input-area">
				<!-- 语音/键盘切换图标 -->
				<image class="action-icon voice-icon"
					:src="voiceMode ? require('@/static/chat/icon_jp.png') : require('@/static/chat/icon_fyy.png')"
					@click="toggleVoiceMode">
				</image>
				<!-- <image class="action-icon voice-icon" src="@/static/chat/icon_fyy.png" @click="handleVoiceInput"></image> -->
				<input v-if="!voiceMode" class="chat-input" type="text" v-model="currentInputContent"
					:placeholder="inputPlaceholder" placeholder-style="color:#C0C0C0;" :cursor-spacing="10"
					@focus="onInputFocus" @confirm="handleSendMessage" confirm-type="send" />
				<!-- 按住说话按钮 -->
				<view v-if="voiceMode" class="voice-input-button" @touchstart="handleRecordStart"
					@touchend="handleRecordEnd" :class="{'recording': isRecording}">
					{{ isRecording ? '松开 结束' : '按住 说话' }}
				</view>
				<image class="action-icon smiley-icon" src="@/static/chat/icon_wx.png" @click="handleSmiley"></image>
				<image v-if="!currentInputContent.trim() && !isSending" class="action-icon plus-icon"
					src="@/static/chat/icon_jh.png" @click="toggleOptionsPanel"></image>
				<button v-else-if="currentInputContent.trim() && !isSending" class="send-button"
					@click="handleSendMessage">发送</button>
				<u-loading-icon v-if="isSending" mode="circle" class="send-loading-icon"></u-loading-icon>
			</view>
			<view class="options-panel" :class="{ 'panel-open': isOptionsPanelOpen }">
				<view class="options-grid">
					<view class="option-item" v-for="option in panelOptions" :key="option.text"
						@click="handlePanelOption(option.action)">
						<view class="option-icon-background">
							<image :src="option.icon" class="option-icon"></image>
						</view>
						<text class="option-text">{{ option.text }}</text>
					</view>
				</view>
			</view>
			<!-- 新增：音视频通话类型选择 -->
			<u-action-sheet :actions="callTypeActions" :show="showCallTypeSheet" cancelText="取消"
				@select="handleCallTypeSelect" @close="showCallTypeSheet = false"></u-action-sheet>
			<!-- 图片/视频选择操作表 -->
			<u-action-sheet :actions="mediaTypeActions" :show="showMediaTypeSheet" cancelText="取消"
				@select="handleMediaTypeSelect" @close="showMediaTypeSheet = false"></u-action-sheet>
		</view>
		<!-- 录音状态提示浮层 -->
		<view class="recording-overlay" v-if="isRecording">
			<u-icon name="mic" color="rgba(248, 248, 248, 1)" size="16rpx"></u-icon>
			<text>正在录音...</text>
		</view>
	</view>
</template>

<script>
	import {
		mapState,
		mapActions,
		mapGetters
	} from 'vuex';
	import imService from '@/utils/imService.js';
	import NavBar from "@/components/common-nav/common-nav.vue"
	import DomVideoPlayer from '@/components/DomVideoPlayer/DomVideoPlayer.vue'; // 引入视频播放器
	import {
		postMessageReport,
		getMessagesList,
		postConversationLeave,
		serviceTypeList
	} from '@/request/api.js'
	const ALBUM_ICON = require("@/static/chat/icon_xc.png");
	const CAMERA_ICON = require("@/static/chat/icon_xj.png");
	const VOICECALL_ICON = require("@/static/chat/icon_dh.png")
	const LOCATION_ICON = require("@/static/chat/icon_dw.png")
	const FAVORITE_ICON = require("@/static/chat/icon_sc.png")
	const MAX_HISTORY_PER_FETCH = 20
	export default {
		components: {
			NavBar
		},
		data() {
			return {
				loading: true,
				isBottomMenuExpanded: true, // 控制底部菜单展开状态
				isBottomMenuInitialized: false, // 添加这个属性
				bottomMenu: [],
				scrollView: {
					refresherTriggered: false,
					intoView: "",
					safeAreaHeight: 0,
					anchorId: null, // 用于存储锚点消息的ID
				},
				ajax: {
					rows: 20,
					page: 1,
					flag: true,
				},
				currentInputContent: '',
				isGroup: false,
				isQuote: false,
				quoteInfo: {},
				isOptionsPanelOpen: false,
				inputPlaceholder: "速来唠嗑! 请注意文明发言~",
				isSending: false,
				panelOptions: [{
						icon: ALBUM_ICON,
						text: '相册',
						action: 'album'
					},
					{
						icon: CAMERA_ICON,
						text: '拍摄',
						action: 'camera'
					},
					{
						icon: VOICECALL_ICON,
						text: '语音通话',
						action: 'voicecall'
					},
					{
						icon: LOCATION_ICON,
						text: '定位',
						action: 'location'
					},
					{
						icon: FAVORITE_ICON,
						text: '收藏',
						action: 'favorite'
					},
				],
				messages: [], // 本地消息列表，可以从 Vuex 获取或自己管理
				currentInputContent: '',


				/*******************************环信***************************************/
				myAvatar: this.$store.state.userInfo.avatar, // 从Vuex或默认
				defaultMyAvatar: '', // 确保路径正确
				defaultOtherAvatar: '', // 确保路径正确

				talkList: [], // 初始化为空数组
				currentInputContent: '',
				inputPlaceholderText: "速来唠嗑! 请注意文明发言~",
				isSendingMessage: false, // 替换原有的 isSending

				isExtraOptionsPanelOpen: false,
				keyboardHeight: 0,
				/*******************************历史记录***************************************/
				isLoadingMoreHistory: false,
				historyPageNum: 1, // 当前历史消息页码
				messageTotal: 0, // 新增：用于存储消息总数
				canLoadMoreHistory: true,
				isScrollViewAnimated: true,
				preloadThreshold: 550, // 新增：预加载阈值，单位px
				isThrottled: false, // 新增：节流阀标志
				/**********************************************************************/

				isQuoting: false,
				quoteTargetInfo: {
					id: null,
					name: '',
					content: '',
					type: ''
				},
				/*******************************视频***************************************/
				showMediaTypeSheet: false, // 新增：控制媒体类型选择表的显示
				mediaTypeActions: [{
					name: '图片'
				}, {
					name: '视频'
				}],
				currentSourceType: '', // 新增：记录当前是 'album' 还是 'camera'
				/*******************************语音***************************************/
				voiceMode: false, // 是否为语音输入模式
				isRecording: false, // 是否正在录音
				recorderManager: null, // 录音管理器实例
				playingMessageId: null, // 用于跟踪正在播放的语音消息ID
				innerAudioContext: null, // 音频播放实例
				/*******************************通话***************************************/
				showCallTypeSheet: false, // 控制通话类型选择表的显示
				callTypeActions: [{
						name: '语音通话',
						type: 'voice'
					},
					{
						name: '视频通话',
						type: 'video'
					}
				],
				showList: false, //如果是向导则为true
				guideUserId: null
			}
		},
		computed: {
			...mapState('im', ['imUserId', 'isConnected']),
			pageHeight() {
				const safeAreaHeight = this.scrollView.safeAreaHeight;
				if (safeAreaHeight > 0) {
					return `height: calc(${safeAreaHeight}px - var(--window-top));`
				}
				return "height: calc(100vh - var(--window-top));";
			},
			pageHeightStyle() { // 动态计算滚动区域高度，考虑键盘
				// 注意: var(--window-bottom) 是H5和小程序概念，App端可能需要用plus API获取
				// #ifdef H5 || MP
				const bottomSafeArea = uni.getSystemInfoSync().safeAreaInsets.bottom || 0;
				// #endif
				// #ifndef H5 || MP
				const bottomSafeArea = 0; // App端通常不需要额外处理底部安全区，除非自定义tabbar
				// #endif
				return `height: calc(100vh - var(--window-top) - ${this.keyboardHeight}px - ${bottomSafeArea}px);`;
			},
		},
		onLoad(options) {
			if (options.user) {
				try {
					// 解码并解析 JSON 字符串
					const item = JSON.parse(decodeURIComponent(options.user));
					this.guideUserId = null
					// 如果isGuide值为1 则是向导，才调用接口获取服务列表
					if (item.isGuide == 1) {
						this.guideUserId = item.userId
						this.initPageData(item.userId)
					} else {
						// 如果不是向导，确保不显示底部菜单
						this.showList = false
					}
				} catch (error) {
					console.error('解析数据失败:', error);
					// 解析失败时确保不显示底部菜单
					this.showList = false
				}
			} else {
				// 如果没有user参数，确保不显示底部菜单
				this.showList = false
			}
			// 初始化菜单状态
			this.$nextTick(() => {
				this.isBottomMenuInitialized = true;
			});
			const OTHER_USER = JSON.parse(options.user)
			this.isGroup = options.group === 'true' || options.group === true;
			// #ifdef H5
			this.scrollView.safeAreaHeight = uni.getSystemInfoSync().safeArea.height;
			// #endif
			this.targetUserId = OTHER_USER.userId;
			this.targetUserAvatar = OTHER_USER.avatar || this.defaultOtherAvatar; // 从路由获取或使用默认
			this.targetUserName = OTHER_USER.nickName || OTHER_USER.userId; // 从路由获取或使用ID

			// --- 加载历史消息的逻辑 ---
			this.getHistoryMessages(true); // 加载初始/历史消息
			// const cachedHistory = this.$store.getters.getChatHistoryByUserId(this.targetUserId);
			this.initData()
			// if (cachedHistory && cachedHistory.length > 0) {
			// 	console.log('从本地缓存加载聊天记录。', cachedHistory.map(res => res.id));
			// 	this.talkList = JSON.parse(JSON.stringify(cachedHistory)); // 使用深拷贝
			// 	this.loading = false;
			// 	this.canLoadMoreHistory = false; // 假设缓存是完整的，不再从服务器拉取旧消息
			// 	this.$nextTick(() => {
			// 		this.scrollToBottom();
			// 	});
			// } else {
			// 	console.log('本地无缓存，从服务器加载历史消息。');
			// 	this.getHistoryMessages(true); // 如果没有缓存，则从环信服务器加载
			// }
			// -----------------------
			uni.setNavigationBarTitle({
				title: this.targetUserName
			});

			uni.onKeyboardHeightChange(res => {
				this.keyboardHeight = res.height;
				if (res.height > 0 && this.isExtraOptionsPanelOpen) {
					this.isExtraOptionsPanelOpen = false;
				}
				// 确保滚动到底部，尤其是在键盘弹起后可能需要
				this.$nextTick(() => this.scrollToBottom(false));
			});
			// 初始化录音管理器
			this.recorderManager = uni.getRecorderManager();
			this.setupRecorderListeners(); // 设置监听
			// 监听 imService 事件
			imService.eventBus.$on('im_local_message_added', this.handleLocalMessageAdded);
			imService.eventBus.$on('im_message_status_updated', this.handleMessageStatusUpdated);
			imService.eventBus.$on('im_message_received', this.handleIncomingMessage); // 监听实际收到的消息
		},
		onUnload() {
			// 在页面卸载时保存聊天记录 ---
			if (this.talkList && this.talkList.length > 0) {
				console.log(`正在保存与 ${this.targetUserId} 的聊天记录到本地。`);
				this.$store.commit('SAVE_CHAT_HISTORY', {
					userId: this.targetUserId,
					history: this.talkList
				});
			}
			// --------------------------------------
			// uni.offKeyboardHeightChange();
			imService.eventBus.$off('im_local_message_added', this.handleLocalMessageAdded);
			imService.eventBus.$off('im_message_status_updated', this.handleMessageStatusUpdated);
			imService.eventBus.$off('im_message_received', this.handleIncomingMessage);
			// 页面卸载时销毁音频实例
			if (this.innerAudioContext) {
				this.innerAudioContext.destroy();
			}
			this.leaveConversationLeave()
		},
		methods: {
			...mapActions('im', ['enterConversation', 'sendTextMessage', 'sendImageMessage', 'sendVideoMessage',
				'sendVoiceMessage'
			]),
			/**
			 * 格式化时间戳，用于聊天界面显示
			 * @param {number} timestamp - 消息的毫秒时间戳
			 * @returns {string} - 格式化后的时间字符串
			 */
			initData() {
				// // 后端接口标记已读
				this.enterConversation(this.targetUserId)
			},

			toggleBottomMenu() {
				this.isBottomMenuExpanded = !this.isBottomMenuExpanded;
				// 添加一个小延迟确保状态更新后触发重渲染
				this.$nextTick(() => {
					// 确保组件重新渲染
				});
			},
			// 跳转到下单页
			handleToCreate(title) {
				uni.navigateTo({
					url: '/pages_sub/order/create-order?userId=' + this.guideUserId + '&title=' + title
				})
			},
			// 离开聊天会话
			leaveConversationLeave() {
				postConversationLeave().then()
			},
			// 获取向导服务列表
			initPageData(userId) {
				let params = {
					userId,
				}
				serviceTypeList(params).then(res => {
					if (res.data.code == 200) {
						let serverList = res.data.rows
						if (serverList && serverList.length > 0) {
							// 在 data 中定义 bottomMenu
							this.bottomMenu = []

							serverList.forEach(item => {
								if (item.typeName == '预约地陪') {
									this.bottomMenu.push({
										icon: '/static/user/chat/dipei.png',
										title: '预约地陪'
									})
								} else if (item.typeName == '定制攻略') {
									this.bottomMenu.push({
										icon: '/static/user/chat/gonglue.png',
										title: '定制攻略'
									})
								} else if (item.typeName == '线上咨询') {
									this.bottomMenu.push({
										icon: '/static/user/chat/zixun.png',
										title: '线上咨询'
									})
								}
							})

							// 只有当 bottomMenu 有内容时才显示
							this.showList = this.bottomMenu.length > 0
						} else {
							// 如果接口返回空数组，则不显示
							this.showList = false
						}
					} else {
						// 如果接口请求失败，则不显示
						this.showList = false
					}
				}).catch(err => {
					console.error('获取服务类型列表失败:', err)
					// 如果接口请求异常，则不显示
					this.showList = false
				})
			},
			formatTimestampForChat(timestamp) {
				if (!timestamp) return '';

				const now = new Date();
				const msgDate = new Date(timestamp);

				const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
				const startOfYesterday = startOfToday - 24 * 60 * 60 * 1000;

				// 计算本周的开始时间（周一为一周的开始）
				const dayOfWeek = now.getDay(); // 0是周日, 1是周一, ..., 6是周六
				const diff = now.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1); // 调整到周一
				const startOfWeek = new Date(now.setDate(diff)).setHours(0, 0, 0, 0);

				const startOfYear = new Date(now.getFullYear(), 0, 1).getTime();

				const hours = msgDate.getHours().toString().padStart(2, '0');
				const minutes = msgDate.getMinutes().toString().padStart(2, '0');

				if (msgDate.getTime() >= startOfToday) {
					// 今天: 显示 HH:mm
					return `${hours}:${minutes}`;
				} else if (msgDate.getTime() >= startOfYesterday) {
					// 昨天: 显示 昨天 HH:mm
					return `昨天 ${hours}:${minutes}`;
				} else if (msgDate.getTime() >= startOfWeek) {
					// 本周: 显示 周X HH:mm
					const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
					return `${weekdays[msgDate.getDay()]} ${hours}:${minutes}`;
				} else if (msgDate.getTime() >= startOfYear) {
					// 今年: 显示 MM月DD日 HH:mm
					const month = (msgDate.getMonth() + 1).toString().padStart(2, '0');
					const day = msgDate.getDate().toString().padStart(2, '0');
					return `${month}月${day}日 ${hours}:${minutes}`;
				} else {
					// 更早: 显示 YYYY年MM月DD日
					const year = msgDate.getFullYear();
					const month = (msgDate.getMonth() + 1).toString().padStart(2, '0');
					const day = msgDate.getDate().toString().padStart(2, '0');
					return `${year}年${month}月${day}日`;
				}
			},
			formatMessageForDisplay(msg, prevMsgTimestamp = null) {
				// 从imService获取格式化时间的能力，或者直接在此实现
				// const displayTime = imService.formatDisplayTime(msg.timestamp || msg.time, prevMsgTimestamp);
				const displayTime = this.formatTimestampForChat(msg.timestamp || msg.time);
				let content = msg.msg; // 默认为文本内容

				if (msg.contentType === 'img') content = msg.url; // 图片用URL
				else if (msg.contentType === 'audio') content = msg.url; // 语音也用URL，时长在duration字段
				else if (msg.contentType === 'video') content = msg.url; // 语音也用URL，时长在duration字段
				else if (msg.contentType !== 'txt' && !msg.content && !msg.msg) content =
					`[${msg.contentType || msg.type || '未知类型'}]`;
				let returndata = {
					id: msg.id || msg.localId, // 本地发送时是localId, 收到的是id
					localId: msg.localId || msg.id, // 确保有个本地唯一标识符
					serverId: msg.serverId,
					content: content,
					contentType: msg.contentType || msg.type,
					type: msg.type === 1 || msg.from === this.imUserId ? 1 : 0, // 1 for sent, 0 for received
					avatar: (msg.type === 1 || msg.from === this.imUserId) ? this.myAvatar : this.targetUserAvatar,
					userId: msg.userId || msg.from,
					name: (msg.type === 1 || msg.from === this.imUserId) ? (this.$store.state.userInfo.nickName ||
						"我") : this.targetUserName,
					timestamp: msg.timestamp || msg.time,
					time: displayTime, //  null or formatted string
					status: msg.status || (msg.type === 1 ? 'sent' : 'received'), // sent, sending, failed, received
					duration: msg.duration ? msg.duration : 3,
					quoteInfo: msg.quoteInfo || (msg.ext?.quote ? {
						id: msg.ext.quote.msgId,
						name: msg.ext.quote.senderName,
						content: msg.ext.quote.quoteContent,
						type: msg.ext.quote.msgType,
					} : null),
					// 如果是从SDK直接来的消息，需要适配字段
					// 例如： sdkMsg.body.type -> contentType, sdkMsg.body.msg -> content
				}
				console.log(msg.type, msg.from, this.imUserId, '判断消息来源');
				return returndata
			},

			/**
			 * @description 通过后端接口获取历史消息 (已更新为使用 total 字段)
			 * @param {boolean} isReset - 是否为重置加载（首次加载或下拉刷新）
			 */
			async getHistoryMessages(isReset = false) {
				// 如果已经确定没有更多历史消息，并且不是重置操作，则直接返回
				if (!this.canLoadMoreHistory && !isReset) {
					return;
				}
				if (this.isLoadingMoreHistory) return;

				this.isLoadingMoreHistory = true;
				if (isReset) {
					this.historyPageNum = 1;
					this.talkList = [];
					this.messageTotal = 0; // 重置时清空总数，确保可以加载
					this.canLoadMoreHistory = true; // 重置时恢复可加载状态
					this.scrollView.anchorId = null; // 重置时清空锚点
				}

				let params = {
					pageNum: this.historyPageNum,
					pageSize: 100,
					userIdForQuery: this.imUserId + '',
					targetIdForQuery: this.targetUserId,
					isGroup: this.isGroup ? 1 : 0
				};

				try {
					const res = await getMessagesList(params);
					this.loading = false;

					// 关键：从接口获取总消息数并存储
					this.messageTotal = res.data.total;

					if (res.data.rows && res.data.rows.length > 0) {
						this.historyPageNum++;
						const firstMessageIdBeforeLoad = this.talkList.length > 0 ? this.talkList[0].id : null;

						const data = res.data.rows.reverse().map(msg => {
							let displayTime = this.formatTimestampForChat(msg.createTime);
							let content = msg.content; // 默认为文本内容
							if (msg.type === 'img') content = msg.fileUrl; // 图片用URL
							else if (msg.type === 'audio') content = msg.fileUrl; // 语音也用URL，时长在duration字段
							else if (msg.type === 'video') content = msg.fileUrl; // 语音也用URL，时长在duration字段
							// 消息格式化映射逻辑
							return {
								type: msg.senderId == this.imUserId ? 1 : 0,
								id: msg.messageId,
								content: content,
								contentType: msg.type,
								avatar: msg.senderId == this.imUserId ? this.myAvatar : this.targetUserAvatar,
								userId: msg.senderId,
								name: msg.senderId == this.imUserId ? (this.$store.state.userInfo.nickName ||
									"我") : this.targetUserName,
								timestamp: new Date(msg.createTime).getTime(),
								time: displayTime,
								status: 'sent',
								duration: msg.duration || 3,
								quoteInfo: msg.quoteInfo // ... etc
							};
						});



						// 1. 暂时关闭滚动动画，防止平滑滚动效果
						this.isScrollViewAnimated = false;

						// 2. 将新数据添加到列表前面
						this.talkList.unshift(...data);
						console.log(this.talkList);
						// 3. 根据当前列表长度和总数，更新是否还有更多历史消息的标志
						this.canLoadMoreHistory = this.talkList.length < this.messageTotal;
						// 4. 使用 $nextTick 确保 DOM 更新完毕
						this.$nextTick(() => {
							console.log("this.scrollView.anchorId", this.scrollView.anchorId);
							if (isReset) {
								// **首次加载或刷新：无动画滚动到底部**
								this.isScrollViewAnimated = false; // 关闭动画
								this.scrollToBottom(); // 调用滚动到底部的方法
								setTimeout(() => {
									this.isScrollViewAnimated = true; // 恢复动画
								}, 150); // 短暂延迟后恢复
							} else if (this.scrollView.anchorId) {

								// **加载更多：无感加载，恢复到锚点位置**
								this.isScrollViewAnimated = false; // 关闭动画
								this.scrollView.intoView = ""; // 先清空，确保能触发更新
								this.$nextTick(() => {
									this.scrollView.intoView = `msg-${this.scrollView.anchorId}`;
									this.scrollView.anchorId = null; // 使用后清空
								});
								setTimeout(() => {
									this.isScrollViewAnimated = true; // 恢复动画
								}, 150);
							}

							// 5. 在短暂延迟后恢复滚动动画，为用户后续操作提供平滑体验
							setTimeout(() => {
								this.isScrollViewAnimated = true;
							}, 150);
						})

					} else {
						// 如果接口返回空数组，也意味着没有更多了
						this.canLoadMoreHistory = false;
					}
				} catch (error) {
					console.error("获取历史消息失败:", error);
					this.loading = false;
					uni.showToast({
						title: "加载历史消息失败",
						icon: "none"
					});
				} finally {
					this.isLoadingMoreHistory = false;
					// 确保每次请求结束后都关闭下拉刷新动画
					this.scrollView.refresherTriggered = false;
				}
			},

			handleLocalMessageAdded({
				conversationId,
				message
			}) {
				console.log("handleLocalMessageAdded新增消息");
				// 确保消息是当前会话的
				if (this.isCurrentConversationMessage(conversationId, message)) {
					console.log('处理本地创建的消息，立即显示在UI', message);
					const lastMsg = this.talkList.length > 0 ? this.talkList[this.talkList.length - 1] : null;
					const formattedMsg = this.formatMessageForDisplay(message, lastMsg ? lastMsg.timestamp : null);

					this.talkList.push(formattedMsg);
					this.$nextTick(() => this.scrollToBottom());
				}
			},
			handleMessageStatusUpdated({
				conversationId,
				localId,
				serverId,
				status,
				newContent
			}) {
				console.log("handleMessageStatusUpdated更新消息状态", );
				if (this.isCurrentConversationMessage(conversationId, {
						to: this.targetUserId,
						from: this.imUserId
					})) { // 粗略判断
					const msgIndex = this.talkList.findIndex(m => m.id === localId || m.localId === localId);
					if (msgIndex > -1) {
						this.$set(this.talkList[msgIndex], 'status', status);
						if (serverId) this.$set(this.talkList[msgIndex], 'serverId', serverId);
						// 如果成功，可以把本地ID替换为服务器ID，便于后续操作（如撤回）
						if (status === 'sent' && serverId) {
							// this.$set(this.talkList[msgIndex], 'id', serverId);
						}
						// 如果有新的内容URL（例如从本地路径更新为网络URL），则更新
						if (newContent) {
							this.$set(this.talkList[msgIndex], 'content', newContent);
						}
					}
				}
			},
			/**
			 * @description 判断是否应该显示当前消息的时间戳。
			 *              此函数现在只关心时间间隔，不关心格式化。
			 * @param {object} currentItem - 当前正在遍历的消息对象
			 * @param {number} index - 当前消息在 talkList 数组中的索引
			 * @returns {boolean} - 返回 true 则显示时间，false 则不显示
			 */
			shouldShowTimestamp(currentItem, index) {
				// 1. 如果消息本身没有可供显示的时间字符串，则不显示。
				//    imService 已经处理了格式化，所以 item.time 会有值。
				if (!currentItem.time) {
					return false;
				}

				// 2. 列表中的第一条消息，总是显示其时间。
				if (index === 0) {
					return true;
				}

				// 3. 获取前一条消息。
				const previousItem = this.talkList[index - 1];

				// 4. 如果前一条消息不存在或没有时间戳，则显示当前消息的时间（作为安全保障）。
				if (!previousItem || !previousItem.timestamp) {
					return true;
				}

				// 5. 计算两条消息之间的时间差（单位：毫秒）。
				const timeDifference = currentItem.timestamp - previousItem.timestamp;

				// 6. 定义5分钟的毫秒数。
				const fiveMinutesInMillis = 5 * 60 * 1000;

				// 7. 如果时间差大于5分钟，则返回 true，表示需要显示时间。
				return timeDifference > fiveMinutesInMillis;
			},
			handleIncomingMessage(sdkMessage) { // sdkMessage 是从 imService.eventBus 传来的原始SDK消息或已部分处理的消息
				if (this.isCurrentConversationMessage(sdkMessage.to, sdkMessage)) { // 需要一个辅助方法判断
					const lastMsg = this.talkList.length > 0 ? this.talkList[this.talkList.length - 1] : null;
					const formattedMsg = this.formatMessageForDisplay(sdkMessage, lastMsg ? lastMsg.timestamp : null);

					// 防重复，如果消息ID已存在（例如是自己发送的回执），则更新状态而不是添加
					const existingMsgIndex = this.talkList.findIndex(m => m.id === formattedMsg.id || m.localId ===
						formattedMsg.id);
					if (existingMsgIndex > -1 && this.talkList[existingMsgIndex].status === 'sending') {
						this.$set(this.talkList, existingMsgIndex, {
							...this.talkList[existingMsgIndex],
							...formattedMsg,
							status: 'sent'
						});
					} else if (existingMsgIndex === -1) { // 真正的新消息
						this.talkList.push(formattedMsg);
					}

					this.$nextTick(() => this.scrollToBottom());
					if (formattedMsg.type === 0) { // 对方发送的消息
						this.enterConversation(this.targetUserId)
					}
				}
			},
			// 辅助方法：判断消息是否属于当前会话
			isCurrentConversationMessage(idOrTarget, msgDetails) {
				console.log(idOrTarget, msgDetails, this.targetUserId);
				// return (msgDetails.from === this.targetUserId && msgDetails.to === this.imUserId) || (msgDetails.from === this.imUserId && msgDetails.to === this.targetUserId)
				// if (msgDetails.chatType === 'singleChat') {
				// 	return (msgDetails.from === this.targetUserId && msgDetails.to === this.imUserId) ||
				// 		   (msgDetails.from === this.imUserId && msgDetails.to === this.targetUserId);
				// } else if (msgDetails.chatType === 'groupChat') {
				// 	return msgDetails.to === this.targetUserId; // targetUserId 此时是群ID
				// }
				return true;
			},
			scrollToBottom() {
				if (this.talkList.length > 0) {
					const lastId = this.talkList[this.talkList.length - 1].id;
					this.scrollView.intoView = ""; // 确保能触发更新
					this.$nextTick(() => {
						this.scrollView.intoView = `msg-${lastId}`;
					});
				}
			},
			onTip(e, item) {
				if (e === 0 || e === "删除") { // u-tooltip might return index or text
					this.onRemove(item.id);
				} else if (e === 1 || e === "引用") {
					this.isQuote = true;
					this.quoteInfo = {
						name: item.name,
						content: item.content
					};
					this.currentInputContent = '';
					this.inputPlaceholder = `回复 ${item.name}: `;
					uni.hideKeyboard();
					const inputField = this.$scope.selectComponent('.chat-input');
					if (inputField && inputField.focus) {
						inputField.focus();
					}
				}
			},
			onRemove(idToDelete) {
				uni.showModal({
					title: '',
					content: '确认删除？',
					showCancel: true,
					cancelText: '取消',
					confirmText: '删除',
					success: res => {
						if (res.confirm) {
							let index = this.talkList.findIndex(item => item.id === idToDelete);
							if (index !== -1) {
								this.talkList.splice(index, 1);
							}
						}
					}
				});
			},
			onOther(id) {
				if (id) {
					uni.navigateTo({
						url: '/pages_sub/user/others/others?userId=' + id,
					});
				}
			},
			/**
			 * 新增：处理滚动事件，用于实现预加载
			 * @param {object} e - 滚动事件对象
			 */
			handleScroll(e) {
				// 使用节流阀，防止高频触发
				if (this.isThrottled) {
					return;
				}
				this.isThrottled = true;
				setTimeout(() => {
					this.isThrottled = false;
				}, 200); // 节流间隔200ms

				const scrollTop = e.detail.scrollTop;
				console.log("scrollTop", scrollTop);

				// 判断是否满足预加载条件
				if (scrollTop < this.preloadThreshold) {
					// 调用加载更多数据的逻辑
					this.loadMoreHistory();
				}
			},

			/**
			 * 新增：封装加载更多的逻辑，取代之前的 onScrollToUpper
			 */
			loadMoreHistory() {
				// 如果正在加载或没有更多数据了，则不执行
				if (this.isLoadingMoreHistory || !this.canLoadMoreHistory) {
					return;
				}

				console.log("触发预加载..."); // 调试用

				// 在触发加载前，设置锚点为当前列表的第一条消息
				if (this.talkList.length > 0) {
					this.scrollView.anchorId = this.talkList[0].id;
				} else {
					this.scrollView.anchorId = null;
				}

				// 调用加载方法
				this.getHistoryMessages(false);
			},

			handleRefresherRefresh() {
				if (this.isLoadingMoreHistory) {
					this.scrollView.refresherTriggered = false;
					return;
				}
				this.scrollView.refresherTriggered = true;
				this.getHistoryMessages(true);
			},
			async handleSendMessage() {
				const contentToSend = this.currentInputContent;
				const quoteDataForSend = this.isQuoting ? {
					...this.quoteTargetInfo
				} : null;
				if (!contentToSend.trim() && !quoteDataForSend) { // 如果没有引用，内容不能为空
					uni.showToast({
						title: '请输入内容',
						icon: 'none'
					});
					return;
				}

				this.isSendingMessage = true;
				// 先不清空输入框，等本地消息显示后再清
				try {
					// Vuex action (im/sendTextMessage) 内部会通过 eventBus 触发 'im_local_message_added'
					let msg = await this.sendTextMessage({
						to: this.targetUserId + '',
						content: contentToSend,
						chatType: this.isGroup ? 'groupChat' : 'singleChat',
						ext: {
							/* 自定义扩展 */
						},
						quoteInfo: quoteDataForSend
					});
					this.addMessage(msg.message)
					// 成功派发action后，清空输入和引用状态
					this.currentInputContent = '';
					this.isQuoting = false;
					this.quoteTargetInfo = {};
					this.inputPlaceholderText = "速来唠嗑! 请注意文明发言~";
					// this.sendMessage(contentToSend, 'text', quoteDataForSend);
				} catch (error) {
					// 错误通常已在 action 或 imService 中 toast 提示
					// 如果发送失败，localMessageForUI 的状态会被更新为 'failed'
					// 这里不需要恢复输入框内容，因为发送中的消息已显示，失败后可以重试
				} finally {
					this.isSendingMessage = false;
				}
			},
			// 消息后端存储
			addMessage(msg) {
				console.log("addMessage=================", msg);

				let params = {
					id: msg.id,
					from: msg.from,
					to: msg.to,
					type: msg.type,
					content: msg.content,
					content: msg.msg,
					fileUrl: msg.body?.url || msg.url,
					messageTime: msg.time,
					isGroup: this.isGroup ? 1 : 0,
				}
				postMessageReport(params).then(res => {

				})
			},
			handleTooltipClick(actionName, item) {
				// ... (删除逻辑需要适配，引用逻辑已包含)
				if (actionName === '引用') {
					this.isQuoting = true;
					this.quoteTargetInfo = {
						id: item.localId || item.id,
						name: item.name,
						content: item.contentType === 'img' ? '[图片]' : item.content,
						type: item.contentType
					};
					this.inputPlaceholderText = `回复 ${item.name}:`;
					this.isExtraOptionsPanelOpen = false;
					// 尝试聚焦输入框
					// 注意：在uni-app中直接操作DOM元素（如input.focus()）不总是推荐或有效，
					// 可以通过设置input的 :focus="shouldFocus" 属性，并改变shouldFocus变量来触发
				}
				// ... (其他操作如删除、撤回)
			},
			handleImageSend(imageUrl) {
				this.sendMessage(imageUrl, 'image');
				this.scrollToBottom();
			},
			sendMessage(content, contentType = 'txt', quoteInfo = null) {
				const now = new Date();
				const hours = now.getHours().toString().padStart(2, '0');
				const minutes = now.getMinutes().toString().padStart(2, '0');
				let time = `${hours}:${minutes}`;

				const lastMessage = this.talkList[this.talkList.length - 1];
				if (lastMessage && lastMessage.time === time) {
					time = null;
				}

				let data = {
					id: `msg-${new Date().getTime()}`,
					content,
					contentType,
					type: 1,
					avatar: this.myAvatar,
					userId: 1,
					time: time,
					name: "我",
					quoteInfo: quoteInfo
				}
				this.talkList.push(data);
				this.$nextTick(() => {
					this.scrollToBottom();
				});
			},
			toggleOptionsPanel() {
				this.isOptionsPanelOpen = !this.isOptionsPanelOpen;
				if (this.isOptionsPanelOpen) {
					uni.hideKeyboard();
				}
			},
			onInputFocus() {
				if (this.isOptionsPanelOpen) {
					this.isOptionsPanelOpen = false;
				}
				this.scrollToBottom();
			},
			handleVoiceInput() {
				uni.showToast({
					title: '语音输入暂未实现',
					icon: 'none'
				});
			},
			handleSmiley() {
				uni.showToast({
					title: '表情选择暂未实现',
					icon: 'none'
				});
			},
			// 处理底部扩展面板的点击事件
			handlePanelOption(action) {
				this.isOptionsPanelOpen = false;
				switch (action) {
					case 'album':
					case 'camera':
						this.currentSourceType = action; // 记录来源
						this.showMediaTypeSheet = true; // 打开选择表
						break;
					case 'voicecall': // 点击了“语音通话”图标
						this.showCallTypeSheet = true; // 打开选择表
						break;
					default:
						uni.showToast({
							title: `功能 ${action} 暂未实现`,
							icon: 'none'
						});
				}
			},
			// 处理媒体类型选择
			handleMediaTypeSelect(option) {
				if (option.name === '图片') {
					this.chooseAndSendImage(this.currentSourceType);
				} else if (option.name === '视频') {
					this.chooseAndSendVideo(this.currentSourceType);
				}
			},
			// 选择并发送视频
			chooseAndSendVideo(sourceType) {
				uni.chooseVideo({
					sourceType: [sourceType],
					compressed: true, // 建议压缩
					maxDuration: 60,
					success: (res) => {
						// 调用Vuex action发送视频
						this.sendVideoMessage({
							to: this.targetUserId,
							filePath: res.tempFilePath,
							duration: res.duration,
							chatType: this.isGroup ? 'groupChat' : 'singleChat'
						}).then(videores => {
							this.addMessage(videores.message)
						}).catch(err => {
							console.error('从页面调用 sendVideoMessage action 失败:', err);
						});
					},
					fail: (err) => {
						if (err.errMsg.indexOf('cancel') === -1) {
							uni.showToast({
								title: '选择视频失败',
								icon: 'none'
							});
						}
					}
				});
			},
			//选择并发送图片
			chooseAndSendImage(sourceType) {
				uni.chooseImage({
					count: 1,
					sourceType: [sourceType], // 'album' 或 'camera'
					success: (res) => {
						const tempFilePath = res.tempFilePaths[0];
						// 调用 Vuex action 发送图片
						this.sendImageMessage({
							to: this.targetUserId,
							filePath: tempFilePath,
							chatType: this.isGroup ? 'groupChat' : 'singleChat'
						}).then(imgres => {
							this.addMessage(imgres.message)
						}).catch(err => {
							// Action 内部已经 toast 提示，这里可以不用再提示
							console.error('从页面调用 sendImageMessage action 失败:', err);
						});
					},
					fail: (err) => {
						if (err.errMsg.indexOf('cancel') === -1) {
							uni.showToast({
								title: '选择图片失败',
								icon: 'none'
							});
						}
					}
				});
			},

			// 预览图片
			previewImage(url) {
				// 过滤出所有图片消息的URL来进行预览
				const imageUrls = this.talkList
					.filter(item => item.contentType === 'img' && item.content && !item.content.startsWith(
						'blob:')) // 过滤掉本地预览路径
					.map(item => item.content);

				if (imageUrls.length > 0) {
					uni.previewImage({
						urls: imageUrls,
						current: url
					});
				} else {
					// 如果只有一张本地图片，也支持预览
					uni.previewImage({
						urls: [url]
					});
				}
			},
			// 新增：切换语音/文本输入模式
			toggleVoiceMode() {
				this.voiceMode = !this.voiceMode;
				if (this.voiceMode) {
					this.isOptionsPanelOpen = false; // 如果扩展面板是打开的，则关闭它
					uni.hideKeyboard();
				}
			},

			// 新增：设置录音监听
			setupRecorderListeners() {
				this.recorderManager.onStart(() => {
					this.isRecording = true;
					console.log('录音开始');
				});

				this.recorderManager.onStop((res) => {
					this.isRecording = false;
					console.log('录音结束', res);
					if (res.duration < 1000) {
						uni.showToast({
							title: '录音时间太短',
							icon: 'none'
						});
						return;
					}
					// 调用Vuex Action发送语音消息
					let msg = this.sendVoiceMessage({
						to: this.targetUserId,
						filePath: res.tempFilePath,
						duration: Math.ceil(res.duration / 1000), // 转换为秒
						chatType: this.isGroup ? 'groupChat' : 'singleChat',
					}).then(audiores => {
						this.addMessage(audiores.message)
					}).catch(err => {
						console.error('从页面调用 sendVideoMessage action 失败:', err);
					});
				});

				this.recorderManager.onError((err) => {
					this.isRecording = false;
					uni.showToast({
						title: '录音失败，请检查权限',
						icon: 'none'
					});
					console.error('录音失败', err);
				});
			},

			// 新增：处理长按开始录音
			handleRecordStart(e) {
				// uni.vibrateShort(); // 提示用户
				this.recorderManager.start({
					format: 'mp3' // 或 aac，根据你的需求
				});
			},

			// 新增：处理松开结束录音
			handleRecordEnd(e) {
				if (this.isRecording) {
					this.recorderManager.stop();
				}
			},

			// 新增：播放语音消息
			playVoiceMessage(item) {
				// 如果点击的是正在播放的语音，则停止它
				if (this.playingMessageId === item.id) {
					this.innerAudioContext.stop();
					// onStop 回调会自动将 playingMessageId 设置为 null
					return;
				}

				// 如果当前有其他语音正在播放，先销毁旧的实例
				if (this.innerAudioContext) {
					this.innerAudioContext.destroy();
				}

				// 创建新的音频实例
				this.innerAudioContext = uni.createInnerAudioContext();
				this.innerAudioContext.src = item.content; // item.content 是语音的URL

				// 在真正开始播放时，设置当前播放ID
				this.innerAudioContext.onPlay(() => {
					this.playingMessageId = item.id;
				});

				// 监听播放自然结束事件
				this.innerAudioContext.onEnded(() => {
					this.playingMessageId = null;
				});

				// 监听因调用 stop() 而停止的事件
				this.innerAudioContext.onStop(() => {
					this.playingMessageId = null;
				});

				// 监听播放错误事件
				this.innerAudioContext.onError((err) => {
					uni.showToast({
						title: '播放失败',
						icon: 'none'
					});
					this.playingMessageId = null; // 出错时也要清空状态
				});

				// 开始播放
				this.innerAudioContext.play();
			},
			// 处理通话类型选择
			async handleCallTypeSelect(option) {
				uni.showLoading({
					title: '正在呼叫...'
				});

				try {
					// 调用 Vuex Action 发起呼叫
					await this.makeCallAction({
						calleeId: this.targetUserId,
						callType: option.type // 'voice' or 'video'
					});
					uni.hideLoading();
					// 成功发起邀请后，imService会处理导航到通话页面
				} catch (error) {
					uni.hideLoading();
					uni.showToast({
						title: error.message || '呼叫失败',
						icon: 'none'
					});
				}
			},
		}
	}
</script>

<style lang="scss">
	page {
		background-color: #F7F7F7;
		font-size: 28rpx;
		overscroll-behavior-y: contain;
	}


	.chat-container {
		display: flex;
		flex-direction: column;
		background-color: #F7F7F7;
	}

	.chat-scroll-view {
		flex: 1;
		width: 100%;
		overflow-y: auto;
		-webkit-overflow-scrolling: touch;
	}

	.talk-list {
		padding: 20rpx 20rpx 0 20rpx;
		display: flex;
		flex-direction: column;
	}

	.nav-bar {
		height: 100rpx;
		/* #ifdef APP */
		height: 170rpx;
		/* #endif */
		background-color: #FFFFFF;
	}

	.nav-bar-lf {
		display: flex;
		align-items: center;

		.nav-bar-lf-avatar {
			width: 80rpx;
			height: 80rpx;
			border-radius: 50%;
		}

		.nav-bar-lf-name {
			margin-left: 20rpx;
			line-height: 30rpx;

			.sub {
				display: flex;
				align-items: center;
				color: #666;
				font-size: 20rpx;
				margin-top: 10rpx;

				.status {
					width: 15rpx;
					height: 15rpx;
					background: #25CD40;
					border-radius: 50%;
					margin-right: 10rpx;
				}
			}
		}
	}

	.message-group {
		margin-bottom: 20rpx;
	}

	.message-time {
		text-align: center;
		color: #B0B0B0;
		font-size: 24rpx;
		margin-bottom: 20rpx;
		line-height: 1;
	}

	.system-message-wrapper {
		display: flex;
		flex-direction: column;
		align-items: center;
		margin-bottom: 20rpx;

		.message-time {
			margin-bottom: 10rpx;
		}

		.system-message-content {
			background-color: #E0E0E0;
			color: #666666;
			font-size: 24rpx;
			padding: 10rpx 20rpx;
			border-radius: 8rpx;
			display: inline-block;
			max-width: 80%;
			text-align: center;
		}
	}

	.system-message {
		background-color: #E0E0E0;
		color: #666666;
		font-size: 24rpx;
		padding: 10rpx 20rpx;
		border-radius: 8rpx;
		text-align: center;
		margin: 0 auto 20rpx auto;
		max-width: calc(100% - 80rpx);
		display: inline-block;
		align-self: center;
	}

	.message-item .voice-bubble {
		display: flex;
		// align-items: center;
		padding: 16rpx 24rpx;

		.voice-duration {
			margin: 0 10rpx;
			font-size: 28rpx;
			color: #000000;
		}
	}

	.message-item {
		display: flex;
		align-items: flex-start;
		margin-bottom: 30rpx;

		.avatar {
			width: 80rpx;
			height: 80rpx;
			border-radius: 50%;
			flex-shrink: 0;
		}

		.message-bubble-wrapper {
			display: flex;
			flex-direction: column;
			max-width: calc(100% - 120rpx);
		}

		.message-image {
			max-width: 300rpx;
			border-radius: 8rpx;
			display: block;
			background-color: #e0e0e0; // 图片加载时的占位背景色
		}

		.message-bubble {
			padding: 18rpx 24rpx;
			border-radius: 12rpx;
			word-break: break-all;
			line-height: 1.5;
			position: relative;
			min-width: 60rpx;
			box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);

			&.voice-bubble {
				// padding: 18rpx 24rpx;
			}

			.voice-content {
				display: flex;
				align-items: center;
				font-size: 28rpx;

				.voice-icon-received {
					margin-right: 10rpx;
				}

				.voice-icon-sent {
					margin-left: 10rpx;
				}

				.voice-duration {
					min-width: 40rpx;
					text-align: center;
				}

				.white {
					color: #FFFFFF;
				}
			}
		}

		.image-wrapper {
			position: relative;
			display: inline-block; // 让包裹层紧贴图片
		}

		.image-loading-overlay,
		.image-failed-overlay {
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background-color: rgba(0, 0, 0, 0.3);
			display: flex;
			justify-content: center;
			align-items: center;
			border-radius: 8rpx;
		}

		.content-quote {
			background-color: rgba(0, 0, 0, 0.05);
			border-radius: 8rpx;
			padding: 10rpx 15rpx;
			word-break: break-all;
			margin-top: 10rpx;
			font-size: 22rpx;
			color: #666666;
			max-width: 100%;
		}

		&.received {
			.avatar {
				margin-right: 20rpx;
			}

			.message-bubble {
				background-color: #FFFFFF;
				color: #000000;
				border-top-left-radius: 0;

				&::before {
					content: '';
					position: absolute;
					top: 0px;
					left: -18rpx;
					width: 0;
					height: 0;
					border-style: solid;
					border-width: 0 20rpx 20rpx 0;
					border-color: transparent #FFFFFF transparent transparent;
				}
			}

			.message-bubble-wrapper {
				align-items: flex-start;
			}

			.voice-icon-received {
				color: #000000;
			}
		}

		&.sent {
			flex-direction: row-reverse;

			.avatar {
				margin-left: 20rpx;
			}

			.message-bubble {
				background-color: #B0E3FF;
				color: #000000;
				border-top-right-radius: 0;

				&::before {
					content: '';
					position: absolute;
					top: 0px;
					right: -18rpx;
					width: 0;
					height: 0;
					border-style: solid;
					border-width: 0 0 20rpx 20rpx;
					border-color: transparent transparent transparent #B0E3FF;
				}
			}

			.message-bubble-wrapper {
				align-items: flex-end;
			}

			.content-quote {
				background-color: rgba(0, 0, 0, 0.05);
				color: #666666;
				border-left-color: rgba(255, 255, 255, 0.3);
			}

			.voice-icon-sent {
				color: #000;
			}
		}
	}

	.bottom-bar {
		background-color: #FFFFFF;
		border-top: 1rpx solid #E0E0E0;
		padding-bottom: constant(safe-area-inset-bottom);
		padding-bottom: env(safe-area-inset-bottom);
		z-index: 100;

		.input-area {
			display: flex;
			align-items: center;
			padding: 15rpx 20rpx;
			// min-height: 70rpx;

			.action-icon {
				width: 56rpx;
				height: 56rpx;
				flex-shrink: 0;
				padding: 5rpx; // For easier tapping
			}

			.voice-icon {
				margin-right: 15rpx;
			}

			.smiley-icon {
				margin: 0 10rpx;
			}

			.plus-icon {
				margin-left: 5rpx;
			}

			.chat-input {
				flex: 1;
				background-color: #F4F3F1;
				border-radius: 36rpx;
				padding: 0 30rpx;
				height: 72rpx;
				line-height: 72rpx;
				font-size: 28rpx;
				caret-color: #B0E3FF;
			}

			.send-button {
				background-color: #B0E3FF;
				color: #FFFFFF;
				font-size: 28rpx;
				padding: 0 30rpx;
				height: 68rpx;
				line-height: 68rpx;
				border-radius: 34rpx;
				margin-left: 15rpx;
				white-space: nowrap;

				&::after {
					border: none;
				}
			}

			.send-loading-icon {
				margin-left: 20rpx;
				width: 60rpx;
				height: 60rpx;
			}

			.voice-input-button {
				flex: 1;
				background-color: #FFFFFF;
				border: 1rpx solid #E0E0E0;
				border-radius: 12rpx;
				height: 68rpx;
				display: flex;
				justify-content: center;
				align-items: center;
				font-size: 28rpx;
				font-weight: 500;
				color: #333;
				transition: background-color 0.2s;

				&.recording {
					background-color: #C8C9CC;
					color: #FFFFFF;
				}
			}
		}

		.options-panel {
			background-color: #FFFFFF;
			max-height: 0;
			overflow: hidden;
			transition: max-height 0.25s ease-out;

			&.panel-open {
				max-height: 500rpx;
			}

			.options-grid {
				display: flex;
				flex-wrap: wrap;
				padding: 30rpx 15rpx;
			}

			.option-item {
				width: 25%;
				display: flex;
				flex-direction: column;
				align-items: center;
				margin-bottom: 30rpx;

				.option-icon-background {
					width: 100rpx;
					height: 100rpx;
					display: flex;
					justify-content: center;
					align-items: center;
					margin-bottom: 10rpx;
				}

				.option-icon {
					width: 100%;
					height: 100%;
				}

				.option-text {
					font-size: 24rpx;
					color: #666666;
				}
			}
		}
	}

	.bottom-bar-menu {
		padding-bottom: constant(safe-area-inset-bottom);
		padding-bottom: env(safe-area-inset-bottom);
		z-index: 100;
		margin-bottom: 20rpx;
		height: 140rpx;
		display: flex;
		overflow: hidden;

		.bottom-bar-menu-left {
			width: 80rpx;
			text-align: center;
			background-color: rgba(255, 255, 255, 1);
			box-shadow: rgba(153, 153, 153, 0.2);
			border-top-right-radius: 10rpx;
			border-bottom-right-radius: 10rpx;
			color: rgba(77, 77, 86, 1);
			flex-shrink: 0;
			z-index: 10;
			position: relative;

			.bottom-bar-menu-left-top {
				padding-top: 36rpx;
				padding-left: 20rpx;
			}

			.bottom-bar-menu-left-bottom {
				font-size: 20rpx;
				font-weight: bold;
				margin: 20rpx 0 14rpx;
				vertical-align: baseline;
			}
		}

		.menu-items-container {
			display: flex;
			flex-grow: 1;
			overflow: hidden;
		}

		.menu-item {
			margin-left: 20rpx; // 保留这个样式
			background-color: #fff;
			border-radius: 10rpx;
			transition: transform 0.3s ease, opacity 0.3s ease;
			transform: translateX(0);
			opacity: 1;
			flex-shrink: 0;
			z-index: 1;

			.menu-icon {
				width: 80rpx;
				height: 80rpx;
				margin: 14rpx 20rpx 8rpx;

				image {
					width: 100%;
					height: 100%;
				}
			}

			.menu-title {
				font-weight: bold;
				font-size: 24rpx;
				line-height: 24rpx;
				margin: 0 12rpx 14rpx;
				color: rgba(0, 0, 0, 1);
			}
		}

		.menu-item-hidden {
			transform: translateX(-100%);
			opacity: 0;
		}
	}

	::v-deep .custom-tooltip-overlay .u-tooltip__wrapper__text {
		max-width: 400rpx !important;
	}

	::v-deep .u-tooltip__wrapper__text {
		max-width: 400rpx !important;
		color: #000000;
	}

	/* 视频消息样式 */
	.video-wrapper {
		position: relative;
		display: inline-block;
		border-radius: 8rpx;
		overflow: hidden;
		/* 确保子元素不会超出圆角 */
		background-color: #000;

		.video-loading-overlay,
		.video-failed-overlay {
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background-color: rgba(0, 0, 0, 0.5);
			display: flex;
			justify-content: center;
			align-items: center;
		}
	}

	.recording-overlay {
		position: fixed;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		background-color: rgba(0, 0, 0, 0.6);
		color: #ffffff;
		padding: 40rpx;
		border-radius: 20rpx;
		display: flex;
		flex-direction: column;
		align-items: center;
		z-index: 999;

		text {
			margin-top: 20rpx;
			font-size: 26rpx;
		}
	}

	/* ==================== 新增：语音播放动画样式 ==================== */
	/* ==================== 新增：竖线语音播放动画样式 ==================== */
	.voice-content {
		display: flex;
		align-items: center;
		gap: 16rpx; // 控制各元素之间的间距
		// [注意] 为接收方气泡内的图标和文字设置颜色
		color: #333;

		// 播放/暂停按钮
		.play-pause-btn {
			width: 44rpx;
			height: 44rpx;
			background-color: #FFFFFF; // 按钮背景色
			border-radius: 50%;
			position: relative;
			flex-shrink: 0;

			// 默认状态：播放三角
			&::before {
				content: '';
				position: absolute;
				top: 50%;
				left: 55%; // 稍微偏右一点，视觉上更居中
				transform: translate(-50%, -50%);
				width: 0;
				height: 0;
				border-style: solid;
				border-width: 10rpx 0 10rpx 16rpx;
				// [注意] 三角颜色，与发送方气泡背景色相同
				border-color: transparent transparent transparent #B0E3FF;
				transition: all 0.2s ease;
			}
		}

		// 播放状态：暂停双竖线
		&.is-playing .play-pause-btn {

			// 隐藏播放三角
			&::before {
				opacity: 0;
				transform: translate(-50%, -50%) scale(0.5);
			}

			// 显示暂停双竖线 (用::after伪元素)
			&::after {
				content: '';
				position: absolute;
				top: 50%;
				left: 50%;
				transform: translate(-50%, -50%);
				width: 14rpx;
				height: 16rpx;
				// [注意] 双竖线颜色，与发送方气泡背景色相同
				border-color: #B0E3FF;
				border-style: double;
				border-width: 0 4rpx;
				transition: all 0.2s ease;
			}
		}

		// 竖条容器
		.sound-bars {
			display: flex;
			align-items: flex-end;
			gap: 4rpx;
			height: 32rpx; // 容器总高度

			span {
				display: block;
				width: 4rpx;
				background-color: #FFFFFF; // 竖条颜色
				border-radius: 4rpx;
				transition: height 0.3s ease;
			}

			// 静态时的不同高度
			span:nth-child(1) {
				height: 10rpx;
			}

			span:nth-child(2) {
				height: 22rpx;
			}

			span:nth-child(3) {
				height: 16rpx;
			}

			span:nth-child(4) {
				height: 28rpx;
			}

			span:nth-child(5) {
				height: 12rpx;
			}
		}

		// 播放状态：为竖条添加动画
		&.is-playing .sound-bars span {
			// 使用 scaleY 动画性能更好
			animation: sound-wave 1.2s infinite ease-in-out alternate;
		}

		// 为每个竖条设置不同的动画延迟，看起来更自然
		&.is-playing .sound-bars span:nth-child(2) {
			animation-delay: -0.2s;
		}

		&.is-playing .sound-bars span:nth-child(3) {
			animation-delay: -0.5s;
		}

		&.is-playing .sound-bars span:nth-child(4) {
			animation-delay: -0.8s;
		}

		&.is-playing .sound-bars span:nth-child(5) {
			animation-delay: -0.3s;
		}
	}

	// 竖条动画关键帧 (通过缩放Y轴实现)
	@keyframes sound-wave {
		0% {
			transform: scaleY(0.3);
		}

		100% {
			transform: scaleY(1);
		}
	}


	/* 适配发送方消息气泡的颜色 */
	.message-item.sent {
		.voice-content {
			// 发送方气泡内的文字和时长颜色
			color: #000;

			.play-pause-btn {
				background-color: #FFFFFF; // 按钮背景色依然是白色

				// 播放三角的颜色变为黑色
				&::before {
					border-color: transparent transparent transparent #000;
				}
			}

			&.is-playing .play-pause-btn::after {
				// 暂停双竖线的颜色变为黑色
				border-color: #000;
			}

			.sound-bars span {
				// 竖条颜色也变为黑色
				background-color: #000;
			}
		}
	}

	/* 适配接收方消息气泡的颜色 */
	.message-item.received {
		.voice-content {
			// 接收方气泡内的文字和时长颜色
			color: #000;

			.play-pause-btn {
				background-color: #B0E3FF; // 按钮背景色使用发送方气泡的颜色

				// 播放三角的颜色变为白色
				&::before {
					border-color: transparent transparent transparent #FFFFFF;
				}
			}

			&.is-playing .play-pause-btn::after {
				// 暂停双竖线的颜色变为白色
				border-color: #FFFFFF;
			}

			.sound-bars span {
				// 竖条颜色也变为白色
				background-color: #000;
			}
		}
	}
</style>